搜索 K
Appearance
博客正在加载中...
Appearance
之前我们仅仅是消除了表现层对于 service 层的依赖;而 service 层对于 dao 层的依赖,我们还没有消除,例如在 service 层中还有一个 new dao 对象的代码,本文我们就来讲讲如何用注入的方式,降低耦合性
依赖注入,全称 Dependency Injection,简称 DI
IoC 的作用:降低程序间的耦合,依赖关系的管理,对象的创建,就交由 Spring;在当前类需要用到其他类的对象时,由 Spring 为我们提供,我们只需要在配置文件中说明依赖关系,就可以在程序中使用了。
例如我们的 service 层依赖 dao 层,这就是一种依赖关系。
依赖关系的维护,就称之为依赖注入。也就是说,某个对象 A 的创建,依赖另一个对象 B,而我们需要将对象 B“注入”到对象 A 中,这样对象 A 才能创建成功。
能注入的数据有三类:
接下来我们来演示如何注入
public class AccountServiceImpl implements IAccountService {
private String name;
private Integer age;
private Date birthday;
public AccountServiceImpl(String name, Integer age, Date birthday) {
this.name = name;
this.age = age;
this.birthday = birthday;
}
@Override
public void saveAccount() {
System.out.println("service中的saveAccount方法执行了" + name + "," + age + "," + birthday);
}
}注意:如果是经常变化的数据,并不适用于注入的方式。比如一个注册用户的功能,其用户名、密码等信息的每个人都不同的,用配置文件的方式肯定不合适;后续我们对于这种情况怎么处理。本文我们主要讲解依赖之间的关系,Spring 是怎么管理的。
然后我们配置注入,使用的标签是 constructor-arg,标签出现的位置在 bean 标签的内部。标签中的属性:
其中,前 3 个属性是用来定位给哪个属性赋值,后面 2 个属性是决定赋什么值 例如,我们想给 String 属性赋值:
<bean id="accountService" class="com.peterjxl.service.impl.AccountServiceImpl">
<constructor-arg type="java.lang.String" value="Peterjxl"/>
</bean>但如果参数列表中有两个 String 类型,就判断不出到底是给哪个 String 赋值,因此 type 属性通常不能独立完成注入的功能。
如果我们可以用 index 属性,由于通过索引位置能确定一个变量,能独立完成注入,但这种方式要记住索引的位置,如果方法参数有变化还得改,也比较麻烦。
因此,我们一般是用 name 属性,来定位变量。 至此,我们可以给 name 和 age 赋值了:
<bean id="accountService" class="com.peterjxl.service.impl.AccountServiceImpl">
<constructor-arg name="name" value="Peterjxl"/>
<constructor-arg name="age" value="18"/>
</bean>虽然 value 的内容是字符串,但是 Spring 很智能,能帮我们将字符串 18 转为 Integer 类型; 但对于日期类型这种比较复杂的,Spring 就无能为例了,如果我们这样配置:
<bean id="accountService" class="com.peterjxl.service.impl.AccountServiceImpl">
<constructor-arg name="name" value="Peterjxl"/>
<constructor-arg name="age" value="18"/>
<constructor-arg name="birthday" value="2022-5-21"/>
</bean> 运行是会报错的:
Error creating bean with name 'accountService' defined in class path resource [bean.xml]:Unsatisfied dependency expressed through constructor parameter 2: Could not convert argument value of type [java.lang.String] to required type [java.util.Date]大意:构造方法中,索引位置为 2 的参数赋值失败,不能转换 String 类型为 Date 类型 解决方法一:我们将 Date 也用容器来管理,并用 ref 来引用:
<bean id="now" class="java.util.Date"/>
<bean id="accountService" class="com.peterjxl.service.impl.AccountServiceImpl">
<constructor-arg name="name" value="Peterjxl"/>
<constructor-arg name="age" value="18"/>
<constructor-arg name="birthday" ref="now"/>
</bean>此时,是可以正常运行的:
service中的saveAccount方法执行了Peterjxl,18,Mon May 01 21:36:40 CST 2023使用构造函数注入的优缺点:
所以我们一般比较少用构造函数注入的方式。
接下来我们演示使用 set 方法注入的方式,我们创建一个新的类 AccountServiceImpl2:
public class AccountServiceImpl2 implements IAccountService {
private String name;
private Integer age;
private Date birthday;
@Override
public void saveAccount() {
System.out.println("service中的saveAccount方法执行了" + name + "," + age + "," + birthday);
}
}请读者自行生成 getter 和 setter 接下来我们配置 bean.xml,使用 property 来完成注入。property 标签出现的位置:bean 标签的内部
标签的属性:
<!-- set方法注入 -->
<bean id="accountService2" class="com.peterjxl.service.impl.AccountServiceImpl2">
<property name="name" value="Peterjxl"/>
<property name="age" value="18"/>
<property name="birthday" ref="now"/>
</bean> 然后我们在 Client 中演示:
IAccountService as2 = (IAccountService)ac.getBean("accountService2");
as2.saveAccount(); 运行结果:
service中的saveAccount方法执行了Peterjxl,18,Mon May 01 21:49:32 CST 2023 使用 set 方法注入的优缺点:
优势:创建对象时没有明确的限制,可以直接使用默认构造函数(注意,如果有其他的有参的构造方法,那么得定义一个无参的构造方法)
弊端:如果有某个成员必须有值,但没有配置对应的 property 标签,则获取对象时 set 方法没有执行,导致对象创建出来是不能用的
总而言之,我们经常用的 set 方式注入
最后我们来说下集合类型的数据如何注入。我们创建一个新的类 AccountServiceImpl3,并加入一些集合类型的成员变量:
public class AccountServiceImpl3 implements IAccountService {
private String[] myStrs;
private List<String> myList;
private Set<String> mySet;
private Map<String, String> myMap;
private Properties myProps;
@Override
public void saveAccount() {
System.out.println(Arrays.toString(myStrs));
System.out.println(myList);
System.out.println(mySet);
System.out.println(myMap);
System.out.println(myProps);
}
}请注意生成 setter 方法,getter 方法我们暂时不用。 接下来我们配置 bean.xml:我们在 property 标签里使用对应的集合标签,来完成注入:
<!-- 复杂类型/集合类型的注入-->
<bean id="accountService3" class="com.peterjxl.service.impl.AccountServiceImpl3">
<property name="myStrs">
<array>
<value>aaa</value>
<value>bbb</value>
<value>ccc</value>
</array>
</property>
<property name="myList">
<list>
<value>aaa</value>
<value>bbb</value>
<value>ccc</value>
</list>
</property>
<property name="mySet">
<set>
<value>aaa</value>
<value>bbb</value>
<value>ccc</value>
</set>
</property>
<property name="myMap">
<map>
<entry key="aaa" value="111"/>
<entry key="bbb">
<value>222</value>
</entry>
</map>
</property>
<property name="myProps">
<props>
<prop key="testC">111</prop>
<prop key="testD">222</prop>
</props>
</property>
</bean>运行结果:
[aaa, bbb, ccc]
[aaa, bbb, ccc]
[aaa, bbb, ccc]
{aaa=111, bbb=222}
{testD=222, testC=111}其实,对应的标签可以互换。
用于给 List 结构集合类型属性注入的标签是:<list> <array> <set>
用于给 Map 集合类型属性注入的标签是: <map> <props>
结构相同,标签可以互换。例如我们给数组注入时,使用的是 array 标签,其实用 list 标签也可以:
<property name="myStrs">
<list>
<value>aaa</value>
<value>bbb</value>
<value>ccc</value>
</list>
</property>本项目已将源码上传到 GitHub 和 Gitee 上。并且创建了分支 demo3,读者可以通过切换分支来查看本文的示例代码。